package newnes.io.fileformats;
/**
 * @(#)INESFile.java
 *
 *
 * @author DG
 * @version 1.00 2009/3/28
 */

import newnes.io.memory.Rom;
import newnes.io.memory.MemoryConstants;
import newnes.MirrorMode;
import newnes.TvStandard;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.nio.ByteBuffer;

public final class INESFile extends NESFileFormat{
	private static int HEADER_SIZE=16;
	private Rom[] prgRom;
	private Rom[] chrRom;
	private Rom trainerData;
	private INESHeader header;
	private String filepath;
	private ByteBuffer data;
	private String crcString;
	
	public INESFile(String path){
		this.filepath = path;
		header = new INESHeader();
	}
	
	public void load() throws IOException{
		FileInputStream stream = new FileInputStream(this.filepath);	
	    try{
	    	data = ByteBuffer.allocate(stream.available());
	    	readHeader(stream);
	    	if(header.hasTrainer()){
	    		readTrainer(stream);
	    	}
	    	readPrgRom(stream);
	    	readChrRom(stream);
	    }finally{
	    	stream.close();
	    	stream = null;
	    }	
	}
	
	private void readHeader(FileInputStream stream) throws IOException{
		byte[] header_bytes = new byte[HEADER_SIZE];
		stream.read(header_bytes);
		header.setPrgRomSize(header_bytes[4]&0xFF);
		header.setChrRomSize(header_bytes[5]&0xFF);
		header.setPrgRamSize(header_bytes[8]&0xFF);
		if(((header_bytes[6]>>3)&0x1) == 1){
			header.setMirroringMode(MirrorMode.FourScreenMode);
		}else if((header_bytes[6]&0x1) == 1){
			header.setMirroringMode(MirrorMode.VerticalMode);
		}else{
			header.setMirroringMode(MirrorMode.HorizontalMode);
		}
		header.setBatteryEnabled(((header_bytes[6]>>1)&0x1) == 1);
		header.setTrainerEnabled(((header_bytes[6]>>2)&0x1) == 1);
		int mapper_number = header_bytes[6]>>4;
		mapper_number = ((header_bytes[7] >> 4) << 4) | mapper_number;
		header.setMapperNumber(mapper_number);
		if((header_bytes[9]&0x1)==1){
			header.setStandard(TvStandard.PAL);
		}else{
			header.setStandard(TvStandard.NTSC);
		}
		data.put(header_bytes);
	}
	
	private void readPrgRom(FileInputStream stream) throws IOException{
		prgRom = new Rom[header.getPrgRomSize()];
		byte[] buffer = new byte[MemoryConstants.SIZE_16K];
		for(int i = 0;i < prgRom.length;++i){
			prgRom[i] = new Rom(MemoryConstants.SIZE_16K);
			stream.read(buffer);
			prgRom[i].loadData(buffer);
			data.put(buffer);
		}
	}
	
	private void readChrRom(FileInputStream stream) throws IOException{
		if(header.getChrRomSize() <= 0){
			return;
		}
		chrRom = new Rom[header.getChrRomSize()];
		byte[] buffer = new byte[MemoryConstants.SIZE_8K];
		for(int i = 0;i < chrRom.length;++i){
			chrRom[i] = new Rom(MemoryConstants.SIZE_8K);
			stream.read(buffer);
			chrRom[i].loadData(buffer);
			data.put(buffer);
		}
	}
	
	private void readTrainer(FileInputStream stream) throws IOException{
		trainerData = new Rom(512);
		byte[] trainer_data = new byte[512];
		stream.read(trainer_data);
		trainerData.loadData(trainer_data);
		data.put(trainer_data);	
	}
	
	public String getPath(){
		return filepath;
	}
	
	public Rom getPrgRom(int bank){
		return prgRom[bank];
	}
	
	public Rom getChrRom(int bank){
		if(chrRom == null){
			return null;
		}
		return chrRom[bank];	
	}
	
	public int getPrgRomSize(){
		return header.getPrgRomSize();	
	}
	
	public int getChrRomSize(){
		return header.getChrRomSize();
	}
	
	public int getPrgRomSizeInBytes(){
		return header.getPrgRomSize()*MemoryConstants.SIZE_16K;	
	}
	
	public int getChrRomSizeInBytes(){
		return header.getChrRomSize()*MemoryConstants.SIZE_8K;
	}
	
	public Header getHeader(){
		return header;
	}
	
	private void calculateChecksum(){
		CRC32 crc32 = new CRC32();	
		byte[] data = this.data.array();
		crc32.update(data);
		crcString = Long.toString(crc32.getValue(),16);
	}
	
	public String checksum(){
		if(crcString == null){
			calculateChecksum();
		}
		return crcString;
	}
}
